/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.handler.admin;

import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Lists;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.IOUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.params.UpdateParams;
import org.apache.solr.core.CachingDirectoryFactory;
import org.apache.solr.core.DirectoryFactory;
import org.apache.solr.core.SolrCore;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.update.MergeIndexesCommand;
import org.apache.solr.update.processor.UpdateRequestProcessor;
import org.apache.solr.update.processor.UpdateRequestProcessorChain;
import org.apache.solr.util.RefCounted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


class MergeIndexesOp implements CoreAdminHandler.CoreAdminOp {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  @Override
  public void execute(CoreAdminHandler.CallInfo it) throws Exception {
    SolrParams params = it.req.getParams();
    String cname = params.required().get(CoreAdminParams.CORE);
    SolrCore core = it.handler.coreContainer.getCore(cname);
    SolrQueryRequest wrappedReq = null;

    List<SolrCore> sourceCores = Lists.newArrayList();
    List<RefCounted<SolrIndexSearcher>> searchers = Lists.newArrayList();
    // stores readers created from indexDir param values
    List<DirectoryReader> readersToBeClosed = Lists.newArrayList();
    Map<Directory, Boolean> dirsToBeReleased = new HashMap<>();
    if (core != null) {
      try {
        String[] dirNames = params.getParams(CoreAdminParams.INDEX_DIR);
        if (dirNames == null || dirNames.length == 0) {
          String[] sources = params.getParams("srcCore");
          if (sources == null || sources.length == 0)
            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                "At least one indexDir or srcCore must be specified");

          for (int i = 0; i < sources.length; i++) {
            String source = sources[i];
            SolrCore srcCore = it.handler.coreContainer.getCore(source);
            if (srcCore == null)
              throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                  "Core: " + source + " does not exist");
            sourceCores.add(srcCore);
          }
        } else {
          DirectoryFactory dirFactory = core.getDirectoryFactory();
          for (int i = 0; i < dirNames.length; i++) {
            boolean markAsDone = false;
            if (dirFactory instanceof CachingDirectoryFactory) {
              if (!((CachingDirectoryFactory) dirFactory).getLivePaths().contains(dirNames[i])) {
                markAsDone = true;
              }
            }
            Directory dir = dirFactory.get(dirNames[i], DirectoryFactory.DirContext.DEFAULT, core.getSolrConfig().indexConfig.lockType);
            dirsToBeReleased.put(dir, markAsDone);
            // TODO: why doesn't this use the IR factory? what is going on here?
            readersToBeClosed.add(DirectoryReader.open(dir));
          }
        }

        List<DirectoryReader> readers = null;
        if (readersToBeClosed.size() > 0) {
          readers = readersToBeClosed;
        } else {
          readers = Lists.newArrayList();
          for (SolrCore solrCore : sourceCores) {
            // record the searchers so that we can decref
            RefCounted<SolrIndexSearcher> searcher = solrCore.getSearcher();
            searchers.add(searcher);
            readers.add(searcher.get().getIndexReader());
          }
        }

        UpdateRequestProcessorChain processorChain =
            core.getUpdateProcessingChain(params.get(UpdateParams.UPDATE_CHAIN));
        wrappedReq = new LocalSolrQueryRequest(core, it.req.getParams());
        UpdateRequestProcessor processor =
            processorChain.createProcessor(wrappedReq, it.rsp);
        processor.processMergeIndexes(new MergeIndexesCommand(readers, it.req));
      } catch (Exception e) {
        // log and rethrow so that if the finally fails we don't lose the original problem
        log.error("ERROR executing merge:", e);
        throw e;
      } finally {
        for (RefCounted<SolrIndexSearcher> searcher : searchers) {
          if (searcher != null) searcher.decref();
        }
        for (SolrCore solrCore : sourceCores) {
          if (solrCore != null) solrCore.close();
        }
        IOUtils.closeWhileHandlingException(readersToBeClosed);
        Set<Map.Entry<Directory, Boolean>> entries = dirsToBeReleased.entrySet();
        for (Map.Entry<Directory, Boolean> entry : entries) {
          DirectoryFactory dirFactory = core.getDirectoryFactory();
          Directory dir = entry.getKey();
          boolean markAsDone = entry.getValue();
          if (markAsDone) {
            dirFactory.doneWithDirectory(dir);
          }
          dirFactory.release(dir);
        }
        if (wrappedReq != null) wrappedReq.close();
        core.close();
      }
    }
  }
}
